﻿using anydata.Entitys;
using MongoDB.Driver;
using Newtonsoft.Json.Linq;

namespace anydata.Models
{
    public class XDepreciationConfig
    {
        public List<XProperty> Dimensions { get; set; }
        public XProperty DepreciationMethod { get; set; }
        public string YearAverageMethod { get; set; }
        public string? YearAverageMethod2 { get; set; }
        public XProperty DepreciationStatus { get; set; }
        public string AccruingStatus { get; set; }
        public string AccruedStatus { get; set; }
        public XProperty OriginalValue { get; set; }
        public XProperty AccumulatedDepreciation { get; set; }
        public XProperty MonthlyDepreciationAmount { get; set; }
        public XProperty NetWorth { get; set; }
        public XProperty AccruedMonths { get; set; }
        public XProperty UsefulLife { get; set; }
        public XProperty StartDate { get; set; }
        public XProperty? ResidualRate { get; set; }
        public XProperty? ResidualVal { get; set; }
        public List<FormField> Fields { get; set; }
        

        public bool Check()
        {
            var success = Dimensions is null || Dimensions.Count == 0 || DepreciationMethod is null || YearAverageMethod is null || DepreciationStatus is null || AccruingStatus is null || AccruedStatus is null
               || OriginalValue is null || AccumulatedDepreciation is null || MonthlyDepreciationAmount is null || NetWorth is null || AccruedMonths is null || StartDate is null || UsefulLife is null;
            if (YearAverageMethod2 is not null)
            {
                success = ResidualRate is null || ResidualVal is null;
            }
            return success;
        }

        public XDepreciationConfig Initialize()
        {
            Dimensions.ForEach(x => x.Initialize());
            DepreciationMethod.Initialize();
            DepreciationStatus.Initialize();
            OriginalValue.Initialize();
            AccumulatedDepreciation.Initialize();
            MonthlyDepreciationAmount.Initialize();
            NetWorth.Initialize();
            AccruedMonths.Initialize();
            StartDate.Initialize();
            UsefulLife.Initialize();
            ResidualRate?.Initialize();
            ResidualVal?.Initialize();
            Fields = new List<FormField> { DepreciationStatus.field, OriginalValue.field, AccumulatedDepreciation.field, MonthlyDepreciationAmount.field, NetWorth.field };
            Dimensions.ForEach(item => Fields.Add(item.field));
            return this;
        }

        public JToken BuildQuery(TokenModel token)
        {
            return new JObject
            {
                ["match"] = new JObject
                {
                    ["isDeleted"] = false,
                    ["belongId"] = token.BelongId.ToString(),
                    [DepreciationMethod.field.code] = new JObject
                    {
                        ["_in_"] = new JArray
                        {
                            YearAverageMethod,
                            YearAverageMethod2
                        }
                    },
                    [OriginalValue.field.code] = new JObject
                    {
                        ["_gt_"] = 0
                    },
                    [UsefulLife.field.code] = new JObject
                    {
                        ["_gt_"] = 0
                    }
                }
            };
        }

        private static int MonthBetween(DateTime start, DateTime end)
        {
            if (start.Year > end.Year)
            {
                return 0;
            }
            var year = end.Year - start.Year;
            var month = end.Month - start.Month;
            return year * 12 + month;
        }
        public (EntityBase?, List<XChange>) Calculate(CompareContext context, EntityBase before, XSnapshot snapshot, DateTime current)
        {
            var changes = new List<XChange>();
            var method = before[DepreciationMethod.field.code];
            if (method?.ToString() == YearAverageMethod)
            {
                var originalValue = before.GetDoubleValue(OriginalValue.field);
                var accumulated = before.GetDoubleValue(AccumulatedDepreciation.field);
                var afterAccumulated = accumulated;
                var netWorth = originalValue - accumulated;
                var afterNetWorth = netWorth;
                var usefulLife = before.GetIntValue(UsefulLife.field);
                var startDate = before.GetDateValue(StartDate.field);
                if (netWorth <= 0 || startDate is null)
                {
                    return (null, changes);
                }

                var after = before.Clone();
                var rightAccruedMonths = MonthBetween((DateTime)startDate, current);
                var remainderMonth = usefulLife - rightAccruedMonths;
                if (remainderMonth <= 0)
                {
                    afterAccumulated = originalValue;
                    afterNetWorth = 0;
                    if (Math.Round(afterAccumulated, 2) != Math.Round(accumulated, 2))
                    {
                        var change = new XChange(context, before.id, AccumulatedDepreciation.field.code, accumulated, afterAccumulated, snapshot.id);
                        change.SetChangeDimension(after, context.SourceFields);
                        changes.Add(change);
                    }
                }
                else
                {
                    var monthlyDepreciationAmount = Math.Round(afterNetWorth / remainderMonth, 2);
                    after[MonthlyDepreciationAmount.field.code] = Math.Round(monthlyDepreciationAmount, 2);
                    if (monthlyDepreciationAmount > afterNetWorth)
                    {
                        monthlyDepreciationAmount = afterNetWorth;
                    }
                    afterAccumulated += monthlyDepreciationAmount;
                    afterNetWorth -= monthlyDepreciationAmount;
                    remainderMonth -= 1;

                    if (Math.Round(afterAccumulated, 2) != Math.Round(accumulated, 2))
                    {
                        var change = new XChange(context, before.id, AccumulatedDepreciation.field.code, accumulated, afterAccumulated, snapshot.id);
                        change.SetChangeDimension(after, context.SourceFields);
                        changes.Add(change);
                    }
                }
                after[AccumulatedDepreciation.field.code] = afterAccumulated;
                after[NetWorth.field.code] = afterNetWorth;
                after[AccruedMonths.field.code] = usefulLife - remainderMonth;
                after[DepreciationStatus.field.code] = remainderMonth == 0 ? AccruedStatus : AccruingStatus;

                return (after, changes);
            }
            else if (method?.ToString() == YearAverageMethod2)
            {
                var originalValue = before.GetDoubleValue(OriginalValue.field);
                var accumulated = before.GetDoubleValue(AccumulatedDepreciation.field);
                var afterAccumulated = accumulated;
                var netWorth = originalValue - accumulated;
                var afterNetWorth = netWorth;
                var usefulLife = before.GetIntValue(UsefulLife.field);
                var startDate = before.GetDateValue(StartDate.field);
                var residualRate = ResidualRate is null ? 0 : before.GetDoubleValue(ResidualRate.field) / 100;
                var residualVal = residualRate * originalValue;
                var diffVal = netWorth - residualVal;

                if (netWorth <= 0 || startDate is null)
                {
                    return (null, changes);
                }
                startDate = startDate.Value.AddMonths(1);
                var after = before.Clone();
                var rightAccruedMonths = MonthBetween((DateTime)startDate, current);
                var remainderMonth = usefulLife - rightAccruedMonths;
                if (remainderMonth <= 0 || netWorth < residualVal)
                {
                    afterAccumulated = originalValue-residualVal;
                    afterNetWorth = residualVal;
                    if (Math.Round(afterAccumulated, 2) != Math.Round(accumulated, 2))
                    {
                        var change = new XChange(context, before.id, AccumulatedDepreciation.field.code, accumulated, afterAccumulated, snapshot.id);
                        change.SetChangeDimension(after, context.SourceFields);
                        changes.Add(change);
                    }
                }
                else
                {
                    var monthlyDepreciationAmount = Math.Round(diffVal / remainderMonth, 2);
                    after[MonthlyDepreciationAmount.field.code] = Math.Round(monthlyDepreciationAmount, 2);
                    if (monthlyDepreciationAmount > afterNetWorth)
                    {
                        monthlyDepreciationAmount = afterNetWorth;
                    }
                    afterAccumulated += monthlyDepreciationAmount;
                    afterNetWorth -= monthlyDepreciationAmount;
                    remainderMonth -= 1;

                    if (Math.Round(afterAccumulated, 2) != Math.Round(accumulated, 2))
                    {
                        var change = new XChange(context, before.id, AccumulatedDepreciation.field.code, accumulated, afterAccumulated, snapshot.id);
                        change.SetChangeDimension(after, context.SourceFields);
                        changes.Add(change);
                    }
                }
                after[AccumulatedDepreciation.field.code] = afterAccumulated;
                after[NetWorth.field.code] = afterNetWorth;
                after[AccruedMonths.field.code] = usefulLife - remainderMonth;
                after[DepreciationStatus.field.code] = remainderMonth == 0 ? AccruedStatus : AccruingStatus;

                return (after, changes);
            }
            return (null, changes);
        }
        public (FilterDefinition<EntityBase>, UpdateDefinition<EntityBase>)? BuildFilterUpdate(EntityBase after)
        {
            var method = after[DepreciationMethod.field.code];
            if (method?.ToString() == YearAverageMethod || method?.ToString() == YearAverageMethod2)
            {
                var filter = Builders<EntityBase>.Filter.Eq(i => i.id, after.id);
                var update = Builders<EntityBase>.Update.CurrentDate(i => i.updateTime)
                    .Set(MonthlyDepreciationAmount.field.code, after[MonthlyDepreciationAmount.field.code])
                    .Set(AccumulatedDepreciation.field.code, after[AccumulatedDepreciation.field.code])
                    .Set(NetWorth.field.code, after[NetWorth.field.code])
                    .Set(AccruedMonths.field.code, after[AccruedMonths.field.code])
                    .Set(DepreciationStatus.field.code, after[DepreciationStatus.field.code]);
                return (filter, update);
            }
            return null;
        }
    }
}